using Gameloop.Vdf;
using Gameloop.Vdf.Linq;

namespace CodeGen.Natives.Scripts;

public partial class Generators
{
    public class GameEvent
    {
        public GameEvent(string name)
        {
            Name = name;
        }

        public string Name { get; init; }
        public string NamePascalCase => Name.ToPascalCase();
        public List<GameEventKey> Keys { get; set; } = new();
    }

    public class GameEventKey
    {
        public GameEventKey(string name, string type)
        {
            Name = name;
            Type = type;
        }

        public string Name { get; init; }
        public string Type { get; init; }
        public string NamePascalCase => Name.ToPascalCase();
        public string MappedType => Mapping.GetCSharpTypeFromGameEventType(Type);
        public string? Comment { get; set; }

        public string Getter
        {
            get
            {
                if (MappedType == "CCSPlayerController?")
                {
                    return $"GetPlayer(\"{Name}\")";
                }

                return $"Get<{MappedType}>(\"{Name}\")";
            }
        }

        public string Setter
        {
            get
            {
                if (MappedType == "CCSPlayerController?")
                {
                    return $"SetPlayer(\"{Name}\", value)";
                }

                return $"Set<{MappedType}>(\"{Name}\", value)";
            }
        }
    }

    private static HttpClient _httpClient = new HttpClient();
    private static string BaseUrl = "https://raw.githubusercontent.com/SteamDatabase/GameTracking-CS2/master/";

    private static List<string> GameEventFiles = new List<string>()
    {
        "game/csgo/pak01_dir/resource/game.gameevents",
        "game/core/pak01_dir/resource/core.gameevents",
        "game/csgo/pak01_dir/resource/mod.gameevents"
    };

    private static async Task<List<GameEvent>> GetGameEvents()
    {
        var allGameEvents = new Dictionary<string, GameEvent>();

        foreach (string url in GameEventFiles)
            // foreach (string file in Directory.EnumerateFiles(pathToSearch, "*.gameevents", SearchOption.AllDirectories).OrderBy(Path.GetFileName))
        {
            var file = await _httpClient.GetStringAsync($"{BaseUrl}/{url}");
            var deserialized = VdfConvert.Deserialize(file);

            var properties =
                deserialized.Value.Where(x => x.Type == VTokenType.Property);
            foreach (var token in properties)
            {
                var property = (VProperty)token;

                var eventName = property.Key;
                var eventProperties = property.Value as VObject;

                var gameEvent = new GameEvent(eventName);

                foreach (var kvp in eventProperties?.Children()!)
                {
                    if (kvp is VValue or VProperty)
                    {
                        if (kvp is VValue asComment)
                        {
                            if (gameEvent.Keys.Any())
                            {
                                gameEvent.Keys.Last().Comment = asComment.ToString().Trim();
                            }
                        }
                        else if (kvp is VProperty asKvp)
                        {
                            var gameEventKey = new GameEventKey(asKvp.Key, asKvp.Value.ToString().Trim());

                            gameEvent.Keys.Add(gameEventKey);
                        }
                    }
                }

                if (allGameEvents.ContainsKey(gameEvent.Name))
                {
                    foreach (var key in gameEvent.Keys)
                    {
                        if (!allGameEvents[gameEvent.Name].Keys.Any(x => x.Name == key.Name))
                        {
                            allGameEvents[gameEvent.Name].Keys.Add(key);
                        }
                    }
                }
                else
                    allGameEvents[gameEvent.Name] = gameEvent;
            }
        }

        return allGameEvents.Values.ToList();
    }


    public static async Task GenerateGameEvents()
    {
        var allGameEvents = await GetGameEvents();

        // Remove the player_chat event as it's manually implemented
        allGameEvents.RemoveAll(e => e.Name == "player_chat");

        var events = allGameEvents.OrderBy(x => x.NamePascalCase).Select(gameEvent =>
        {
            var propertyDefinition = gameEvent.Keys.OrderBy(p => p.NamePascalCase).Select(key =>
            {
                // Hack for now, since we some params with the same name as their parent.
                var propertyName = key.NamePascalCase == gameEvent.NamePascalCase
                    ? $"{key.NamePascalCase}Param"
                    : key.NamePascalCase;

                return $@"{(!string.IsNullOrEmpty(key.Comment) ? "// " + key.Comment : "")}
    public {key.MappedType} {propertyName}
    {{
        get => {key.Getter};
        set => {key.Setter};
    }}";
            });
            return (gameEvent, $@"
[EventName(""{gameEvent.Name}"")]
public class Event{gameEvent.NamePascalCase} : GameEvent
{{
    public Event{gameEvent.NamePascalCase}(IntPtr pointer) : base(pointer){{}}
    public Event{gameEvent.NamePascalCase}(bool force) : base(""{gameEvent.Name}"", force){{}}
    {string.Join("\n", propertyDefinition)}
}}");
        });

        var template = $@"#nullable enable
using System;
using CounterStrikeSharp.API.Modules.Events;
using CounterStrikeSharp.API.Modules.Entities;
using CounterStrikeSharp.API.Core.Attributes;

namespace CounterStrikeSharp.API.Core;
<content>
#nullable restore
";

        var outputPath = Path.Join(Helpers.GetRootDirectory(), "managed/CounterStrikeSharp.API/Generated/GameEvents");

        // Clear output directory
        if (Directory.Exists(outputPath))
        {
            string[] files = Directory.GetFiles(outputPath, "*", SearchOption.AllDirectories);
            foreach (string file in files)
            {
                File.Delete(file);
            }
        }

        foreach (var (gameEvent, definition) in events)
        {
            File.WriteAllText(Path.Join(outputPath, $"Event{gameEvent.NamePascalCase}.g.cs"), template.Replace("<content>", definition));
        }

        Console.WriteLine($"Generated C# bindings for {allGameEvents.Count} game events successfully.");
    }
}
